﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;
using SSPComm.SSPCommands;
using System.Diagnostics;
using SSPComm.SSPCryption;
using SSPComm.SSPReplies;

namespace SSPComm.SSPTransport
{
    public delegate void ReceivedEvent(SSPReply inEvent);
    public delegate void ThrowedException(Exception inException);

    internal class SSPTransportManager
    {
        private readonly SSPChannel _channel;
        private readonly ReceivedEvent _eventReceiver;
        private readonly ThrowedException _exceptionThrower;
        private readonly TraceListener _logger;
        private readonly Byte _commId;

        private SSPCryptManager _crypter = new SSPCryptManager();
        private Boolean _seq = true;
        private Boolean _stopFlag = true;
        internal static readonly Int32 SSP_POLL_TMO = 200;
        private readonly Timer _tmoPoll = new Timer(SSP_POLL_TMO);
        private readonly Object _sendMonitor = new Object();
        public Boolean IsStarted { get; private set; }

        public SSPTransportManager(String inComPort, Byte inCommId, ReceivedEvent inEventDelegate, ThrowedException inExceptionDelegate)
            :this(inComPort, inCommId)
        {
            _eventReceiver = inEventDelegate;
            _exceptionThrower = inExceptionDelegate;
        }

        public SSPTransportManager(String inComPort, Byte inCommId)
        {
           //Logging in debug mode only!
#if DEBUG
            _logger = new TextWriterTraceListener(String.Concat(ToString(), ".log"));
            Trace.Listeners.Add(_logger);
#endif

            _commId = inCommId;
            _channel = new SSPChannel(inComPort);
            _tmoPoll.AutoReset = false;
            _tmoPoll.Elapsed += new System.Timers.ElapsedEventHandler(_tmoPoll_Elapsed);
        }

        private void ThrowEvent(SSPReply inEvent)
        {
            if (_eventReceiver != null)
            {
                System.Threading.Thread thr = new System.Threading.Thread(delegate(Object inState){
                    _eventReceiver(inEvent);
                });
                thr.Start();
            }
        }


        private void ThrowException(Exception inException)
        {
            if (_exceptionThrower != null)
            {
                System.Threading.Thread thr = new System.Threading.Thread(delegate(Object inState)
                {
                    _exceptionThrower(inException);
                });
                thr.Start();
            }
        }

        void _tmoPoll_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            Poll();
        }


        internal SSPData SendData(SSPData inData)
        {
            lock (_sendMonitor)
            {
                SSPData res;
                Trace.WriteLine(String.Format("{0}\t{1}\t{2}", DateTime.Now.ToString("ss.fff"), ">", inData), ToString());
                if (_crypter.Inited)
                {
                    SSPCommand cmd = SendData(_crypter.Crypt(inData));
                    res = _crypter.Decrypt(cmd.Data);
                }
                else
                {
                    res = SendData(inData.Data).SmileData;
                }
                Trace.WriteLine(String.Format("{0}\t{1}\t{2}", DateTime.Now.ToString("ss.fff"), "<", res), ToString());
                return res;
            }
        }

        internal SSPCommand SendData(Byte[] inData)
        {
            lock (_sendMonitor)
            {
				try
				{
					SSPCommand cmd = new SSPCommand(_seq, _commId, inData);
					SSPCommand res = _channel.SendAndReceive(cmd);
					if (!checkAddress(res))
					{
						throw new SSPException(res.SmileData, "SSP slave answer invalid: address");
					}

					_seq = !_seq;
					return res;
				}
				catch
				{
					_logger.Flush();
					throw;
				}
            }
        }

        private Boolean checkAddress(SSPCommand inCommand)
        {
            return inCommand.SlaveID == _commId;
        }


        public void Start()
        {
            _channel.Open();
            IsStarted = _channel.IsOpen;
            if (!IsStarted)
            {
                throw new Exception("Port is closed.");
            }
            Init();
        }

        public void Stop()
        {
            StopPoll();
            _crypter.Inited = false;
            lock (_sendMonitor)
            {
				if (_channel.IsOpen)
				{
					_channel.Close();
				}
                IsStarted = _channel.IsOpen;
                if (IsStarted)
                {
                    throw new Exception("Port is opened.");
                }
            }
            Trace.Flush();
        }

        internal void StartPoll()
        {
            if (!_stopFlag)
            {
                return;
            }
            _stopFlag = false;
            Poll();
        }

        internal void StopPoll()
        {
            _stopFlag = true;
        }

        private void Poll()
        {
            if (_stopFlag)
            {
                return;
            }
            SSPData replyData;
            if (System.Threading.Monitor.TryEnter(_sendMonitor, 0))
            {
                try
                {
                    replyData = SendData(new SSPPollData());
                    SSPReply rep = SSPReplyParser.Parse(replyData);
                    if (!((rep.RawData.Length == 1) && (rep.Type == SSPDataTypes.OK)))
                    {
                        ThrowEvent(rep);
                    }
                }
                catch(Exception exc)
                {
                    ThrowException(exc);
                }
                finally
                {
                    System.Threading.Monitor.Exit(_sendMonitor);
                }
            }
            _tmoPoll.Start();
        }

        private void Init()
        {
            _seq = true;
            //1. Sync
            SSPData res = SendData(new SSPSyncData());

            if (!res.IsOK)
            {
                throw new SSPException(res, "SSP slave answer invalid: not synched");
            }

            _crypter = new SSPCryptManager();
            SSPData data2send;

            //2. Generator
            data2send = _crypter.GetSSPGeneratorData();
            res= SendData(data2send);

            if (!res.IsOK)
            {
                throw new SSPException(res, "SSP slave answer invalid: generator");
            }

            //3. Modulus
            data2send = _crypter.GetSSPModulusData();
            res = SendData(data2send);
            if (!res.IsOK)
            {
                throw new SSPException(res, "SSP slave answer invalid: modulus");
            }

            //4. Exchange key

            data2send = _crypter.GetSSPExchangeData();
            res = SendData(data2send);
            if (!res.IsOK)
            {
                throw new SSPException(res, "SSP slave answer invalid: exchange key");
            }
            _crypter.SolveSyncKey(res.Data);
            System.Threading.Thread.Sleep(2000);
            _crypter.Inited = true;
        }

        public void Reset()
        {
            StopPoll();
            _crypter.Inited = false;
            Init();
        }

        public override string ToString()
        {
            return "SSPTransportManager";
        } 
    }
}
